package lib.base;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.Period;
import net.fortuna.ical4j.model.PeriodList;
import net.fortuna.ical4j.model.component.VEvent;

import lib.data.Customer;
import lib.data.Department;
import lib.data.Employee;

import com.google.gdata.data.DateTime;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.calendar.CalendarAclRole;
import com.google.gdata.data.calendar.CalendarEntry;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.extensions.When;

/**
 * The Class EgoHolidayManager.
 */
public class EgoHolidayManager extends EgoCalendar
{
	public static String HOLIDAY_TITLE_FORMAT = "Holiday %length% %status%";
	public static String HOLIDAY_DEPARTMENT_TITLE_FORMAT = "Holiday %given name% %last name% %length% %status%";
	
	public static String HOLIDAY_CALENDAR_TITLE_FORMAT = "Holiday - %given name% %last name%";
	public static String HOLIDAY_DEPARTMENT_CALENDAR_TITLE_FORMAT = "Holiday - %name%";
	
	public static String TEST_CALENDAR_TITLE = "_TEST_CAL_";
	
	/**
	 * Instantiates a new ego holiday manager.
	 * 
	 * It can creates new holidays etc.
	 * 
	 * @param username the username
	 * @param password the password
	 * 
	 * @throws Exception the exception
	 * 
	 * @return void
	 */
	public EgoHolidayManager(String username, String password) throws Exception
	{
		super(username, password);
	}
	
	/**
	 * Creates a new Holiday Event for an employee
	 * 
	 * @param holiday EgoHoliday
	 * 
	 * @throws Exception the exception
	 * 
	 * @return void
	 */
	public void newHoliday(EgoHoliday holiday) throws Exception
	{
		Employee emp = holiday.getEmployee();
		String length = holiday.isHalfDay() ? EgoHoliday.LENGTH_HALF : EgoHoliday.LENGTH_FULL;
		
		DateTime start = DateTime.parseDate(holiday.getStartStamp());
		DateTime end = DateTime.parseDate(holiday.getEndStamp());
		// add 1 day for google api
		end.setValue( end.getValue() + 24*60*60*1000 );
		
		When time = new When();
		time.setStartTime(start);
		time.setEndTime(end);
			
		// create entry for employee calendar
		String title = HOLIDAY_TITLE_FORMAT.replaceAll("%length%", length).replaceAll("%status%", holiday.getStatus());
		super.addEntry(
			getCalendar(emp)
			, time
			, title
		);

		// create entry for every department calendar
		String title_dep = HOLIDAY_DEPARTMENT_TITLE_FORMAT
			.replaceAll("%length%", length)
			.replaceAll("%status%", holiday.getStatus())
			.replaceAll("%given name%", emp.getField("givenName").toString())
			.replaceAll("%last name%", emp.getField("lastName").toString());
		ArrayList<Department> deps = emp.getDepartments();
		for (int ii=0; ii<deps.size(); ii++)
		{
			super.addEntry(
				getCalendar(deps.get(ii))
				, time
				, title_dep
			);
		}
	}

	/**
	 * returns all holidays of a employee in a given year
	 * 
	 * @param emp Employee
	 * @param year
	 * @return ArrayList<EgoHoliday>
	 * @throws Exception
	 */
	public ArrayList<EgoHoliday> getHolidays(Employee emp, String year) throws Exception
	{
		Calendar cal = Calendar.getInstance(); /* create the current Calendar of this year */
		
		if (year == null || year == "")
		{
			year = String.valueOf(cal.get(Calendar.YEAR));
		}

		DateTime d_start = DateTime.parseDate(year+"-01-01");
		DateTime d_end = DateTime.parseDate(year+"-12-31");
		
		ArrayList<EgoHoliday> holidays = new ArrayList<EgoHoliday>();
		
		List<CalendarEventEntry> entries = getEntries(
			getCalendar(emp)
			, HOLIDAY_TITLE_FORMAT.replaceAll("%[^%]+%", "")
			, d_start
			, d_end
		);

		// regular expression to get length and status
		String exp = HOLIDAY_TITLE_FORMAT.replaceAll(
				"%status%",
				"("+ EgoHoliday.STATUS_DENIED +"|"+ EgoHoliday.STATUS_REQUESTED +")?"
		);
		exp = exp.replaceAll("%length%", "("+ EgoHoliday.LENGTH_HALF +")?");
		exp = exp.replaceAll(" ", "[ ]?");
		for (int i=0; i<entries.size(); i++)
		{
			// get length,status
			String length;
			String status;
			Pattern r = Pattern.compile(exp);
			Matcher m = r.matcher(entries.get(i).getTitle().getPlainText());
			if (m.find())
			{
				String m1 = m.group(1);
				String m2 = m.group(2);
				if (m1 == null)
				{
					m1 = "";
				}
				if (m2 == null)
				{
					m2 = "";
				}
				if (
					(m1 == "" || m1.equals(EgoHoliday.STATUS_DENIED) || m1.equals(EgoHoliday.STATUS_REQUESTED))
					&& (m2 == "" || m2.equals(EgoHoliday.LENGTH_HALF))
				) {
					status = m1;
					length = m2;
				} else
				{
					status = m2;
					length = m1;
				}
			} else
			{
				throw new EgoException("unexpected entry title syntax. Given:"+entries.get(i).getTitle().getPlainText()+"\nExpected:"+exp);
			}
			
			holidays.add(new EgoHoliday(emp));
			EgoHoliday last_holi = holidays.get(holidays.size()-1);
			if (length.equals( EgoHoliday.LENGTH_HALF ))
			{
				last_holi.setHalfDay(true);
			}
			last_holi.setStatus(status);
			
			// get start stamp
			long cur_stamp = entries.get(i).getTimes().get(0).getStartTime().getValue();
			cal.setTimeInMillis(cur_stamp);
			String stamp = Integer.toString(cal.get(Calendar.YEAR)) + "-"
				+ (cal.get(Calendar.MONTH)<9 ? "0": "") + Integer.toString(cal.get(Calendar.MONTH)+1) + "-"
				+ (cal.get(Calendar.DAY_OF_MONTH)<10 ? "0": "") + Integer.toString(cal.get(Calendar.DAY_OF_MONTH));
			last_holi.setStartStamp(stamp);
			
			// get end stamp
			cur_stamp = entries.get(i).getTimes().get(0).getEndTime().getValue();
			cal.setTimeInMillis(cur_stamp);
			// subtract 1 day because of google api
			cal.add(Calendar.DAY_OF_YEAR, -1);
			stamp = Integer.toString(cal.get(Calendar.YEAR)) + "-"
				+ (cal.get(Calendar.MONTH)<9 ? "0": "") + Integer.toString(cal.get(Calendar.MONTH)+1) + "-"
				+ (cal.get(Calendar.DAY_OF_MONTH)<10 ? "0": "") + Integer.toString(cal.get(Calendar.DAY_OF_MONTH));
			last_holi.setEndStamp(stamp);
		}
		
		return holidays;
	}
	
	/**
	 * get holidays from department
	 * by given Status
	 * 
	 * @param dep Department
	 * @param status String
	 * @param year String null for current year
	 * @return ArrayList<EgoHoliday>
	 * @throws Exception
	 */
	public ArrayList<EgoHoliday> getHolidays(Department dep, String status, String year) throws Exception
	{
		ArrayList<EgoHoliday> holidays = new ArrayList<EgoHoliday>();
		
		ArrayList<Employee> emps = dep.getEmployees();
		for (int i=0; i<emps.size(); i++)
		{
			ArrayList<EgoHoliday> h = getHolidays(emps.get(i), year);
			for (int ii=0; ii<h.size(); ii++)
			{
				if (h.get(ii).getStatus().equals( status ))
				{
					// save corresponding employee 
					h.get(ii).setEmployee(emps.get(i));
					holidays.add(h.get(ii));
				}
			}
		}
		
		return holidays;
	}

	/**
	 * returns all public holidays (stamp) of an employee
	 * @param emp
	 * @param year
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public ArrayList<String> getPublicHolidays(Employee emp, String year) throws Exception
	{
		ArrayList<String> days = new ArrayList<String>();
		
		if (!emp.getField("holidayCal").toString().isEmpty())
		{
			Calendar cal = Calendar.getInstance(); /* create the current Calendar of this year */
			if (year == null)
			{
				year = String.valueOf(cal.get(Calendar.YEAR));
			}
			net.fortuna.ical4j.model.Date rangeStart = new net.fortuna.ical4j.model.Date(year+"0101");
			net.fortuna.ical4j.model.Date rangeEnd = new net.fortuna.ical4j.model.Date(String.valueOf(Integer.parseInt(year)+1)+"0101");
			
			net.fortuna.ical4j.model.DateTime rangeStart_t = new net.fortuna.ical4j.model.DateTime(rangeStart);
			net.fortuna.ical4j.model.DateTime rangeEnd_t = new net.fortuna.ical4j.model.DateTime(rangeEnd);
			
			try {
				CalendarBuilder builder = new CalendarBuilder();
				net.fortuna.ical4j.model.Calendar calendar = builder.build(
					new StringReader( EgoSystem.httpRequest(emp.getField("holidayCal").toString()) )
				);
				ComponentList comps = calendar.getComponents(Component.VEVENT);
				for (int i=0; i<comps.size(); i++)
				{
					VEvent x = (VEvent)(comps.get(i));
					PeriodList periods = x.calculateRecurrenceSet(new Period(rangeStart_t, rangeEnd_t));
					Iterator<Period> it = periods.iterator();
					while (it.hasNext())
					{
						Period p = it.next();
						cal.setTime(p.getStart());
						String stamp = Integer.toString(cal.get(Calendar.YEAR)) + "-"
							+ (cal.get(Calendar.MONTH)<9 ? "0": "") + Integer.toString(cal.get(Calendar.MONTH)+1) + "-"
							+ (cal.get(Calendar.DAY_OF_MONTH)<10 ? "0": "") + Integer.toString(cal.get(Calendar.DAY_OF_MONTH));
						days.add(stamp);
					}
				}
			} catch (Exception e)
			{
				// do nothing
			}
		}
		
		return days;
	}
	
	/**
	 * creates the calendar for an employee
	 * @param emp Employee
	 */
	protected CalendarEntry createCalendar(Employee emp) throws Exception
	{
		return super.createCalendar(
			HOLIDAY_CALENDAR_TITLE_FORMAT
				.replaceAll("%given name%", emp.getField("givenName").toString())
				.replaceAll("%last name%", emp.getField("lastName").toString())
			, emp.getCustomer().getField("customerID").toString()
		);
	}
	
	/**
	 * creates the calendar for a department
	 * @param dep Department
	 */
	protected CalendarEntry createCalendar(Department dep) throws Exception
	{
		return super.createCalendar(
			HOLIDAY_DEPARTMENT_CALENDAR_TITLE_FORMAT
				.replaceAll("%name%", dep.getField("name").toString())
			, dep.getCustomer().getField("customerID").toString()
		);
	}

	/**
	 * creates a test calendar. This is used for testing the movement function
	 * @param Customer cust
	 * @return CalendarEntry
	 * @throws Exception
	 */
	public CalendarEntry createTestCalendar(Customer cust) throws Exception
	{
		return super.createCalendar(
			TEST_CALENDAR_TITLE
			, cust.getField("customerID").toString()
		);
	}
	
	public CalendarEntry getTestCalendar(Customer cust) throws Exception
	{
		// get all calendars
		List<CalendarEntry> list = super.getAllCalendars(cust.getField("customerID").toString());
		String search_name = TEST_CALENDAR_TITLE;
		
		for (int i=0; i<list.size(); i++)
		{
			// name => FORMAT
			if (list.get(i).getTitle().getPlainText().equals( search_name ))
			{
				return list.get(i);
			}
		}
		return null; // no cal
	}
	
	/**
	 * returns the calendar of a given employee
	 * 
	 * @param emp Employee
	 * @return CalendarEntry Calendar
	 * @throws Exception
	 */
	public CalendarEntry getCalendar(Employee emp) throws Exception
	{
		// get all calendars
		List<CalendarEntry> list = super.getAllCalendars(emp.getCustomer().getField("customerID").toString());
		String search_name = HOLIDAY_CALENDAR_TITLE_FORMAT
			.replaceAll("%given name%", emp.getField("givenName").toString())
			.replaceAll("%last name%", emp.getField("lastName").toString());
		
		for (int i=0; i<list.size(); i++)
		{
			// name => FORMAT
			if (list.get(i).getTitle().getPlainText().equals( search_name ))
			{
				return list.get(i);
			}
		}
		return null; // no cal
	}
	
	/**
	 * returns the calendar of a given department
	 * 
	 * @param dep Department
	 * @return CalendarEntry Calendar
	 * @throws Exception
	 */
	public CalendarEntry getCalendar(Department dep) throws Exception
	{
		// get all calendars
		List<CalendarEntry> list = super.getAllCalendars(dep.getCustomer().getField("customerID").toString());
		String search_name = HOLIDAY_DEPARTMENT_CALENDAR_TITLE_FORMAT
			.replaceAll("%name%", dep.getField("name").toString());
		
		for (int i=0; i<list.size(); i++)
		{
			// name => FORMAT
			if (list.get(i).getTitle().getPlainText().equals( search_name ))
			{
				return list.get(i);
			}
		}
		return null; // no cal
	}
	
	/**
	 * checks if a calendar for the given employee exists
	 * and if the permissions are given.
	 * Otherwise the calendar will be created and the permissions were given.
	 * 
	 * @param emp
	 * @throws Exception
	 */
	public void setupCalendar(Employee emp) throws EgoException
	{
		boolean retry = false;
		while (true)
		{
			try {
				CalendarEntry cal = getCalendar(emp);
				if (cal == null)
				{
					cal = createCalendar(emp);
				}
				if (checkUserAccess(cal, emp.getField("account").toString()) == false)
				{
					addUserAccess(cal, emp.getField("account").toString(), CalendarAclRole.READ);
				}
			} catch (Exception e)
			{
				if (retry == true)
				{
					throw new EgoException(EgoException.SETUP_CALENDAR_FAILED, e);
				}
				
				// reset calendar cache and retry
				_allCalendars = null;
				retry = true;
				continue;
			}
			break;
		}
	}

	/**
	 * checks if a calendar for the given department exists
	 * and if the permissions are given.
	 * Otherwise the calendar will be created and the permissions were given.
	 * 
	 * @param dep Department
	 * @throws Exception
	 */
	public void setupCalendar(Department dep) throws Exception
	{
		CalendarEntry cal = getCalendar(dep);
		if (cal == null)
		{
			cal = createCalendar(dep);
		}
		ArrayList<Employee> emps = dep.getEmployees();
		for (int i=0; i<emps.size(); i++)
		{
			if (checkUserAccess(cal, emps.get(i).getField("account").toString()) == false)
			{
				addUserAccess(cal, emps.get(i).getField("account").toString(), CalendarAclRole.READ);
			}
		}
	}
	
	public EgoHoliday getHoliday(Employee emp, String start, String end) throws Exception
	{
		ArrayList<EgoHoliday> all = getHolidays(
			emp
			, start.substring(0, 4) // year - first 4 digits
		);
		for (int i=0; i<all.size(); i++)
		{
			if (
				all.get(i).getStartStamp().equals( start )
				&& all.get(i).getEndStamp().equals( end )
			) {
				return all.get(i);
			}
		}
		return null;
	}
	
	/**
	 * delete a holiday from a given employee
	 * 
	 * @param emp Employee
	 * @param holiday EgoHoliday
	 * @throws Exception
	 */
	public void deleteHoliday(Employee emp, EgoHoliday holiday) throws Exception
	{
		// delete from employee's google calendar
		CalendarEntry cal = getCalendar(emp);
		List<CalendarEventEntry> entries = getEntries(
			cal
			, HOLIDAY_TITLE_FORMAT.replaceAll("%length%", "%").replaceAll("%status%", holiday.getStatus())
			, DateTime.parseDate(holiday.getStartStamp())
			, DateTime.parseDate(holiday.getEndStamp())
		);
		for (int i=0; i<entries.size(); i++)
		{
			deleteEntry(entries.get(i));
			cal.update();
		}
		
		// delete from employee's departments' google calendar
		ArrayList<Department> deps = emp.getDepartments();
		for (int i=0; i<deps.size(); i++)
		{
			List<CalendarEventEntry> entries2 = getEntries(
				getCalendar(deps.get(i))
				, HOLIDAY_DEPARTMENT_TITLE_FORMAT
					.replaceAll("%length%", "%") // joker for length
					.replaceAll("%status%", holiday.getStatus())
					.replaceAll("%given name%", emp.getField("givenName").toString())
					.replaceAll("%last name%", emp.getField("lastName").toString())
				, DateTime.parseDate(holiday.getStartStamp())
				, DateTime.parseDate(holiday.getEndStamp())
			);
			for (int ii=0; ii<entries2.size(); ii++)
			{
				deleteEntry(entries2.get(ii));
			}
		}
	}
	
	/**
	 * update status of a given holiday
	 * 
	 * @param emp Employee
	 * @param holiday Holiday
	 * @param status new Status
	 */
	public void updateHoliday(Employee emp, EgoHoliday holiday, String status) throws Exception
	{
		CalendarEntry cal = getCalendar(emp);
		List<CalendarEventEntry> entries = getEntries(
			cal
			, HOLIDAY_TITLE_FORMAT.replaceAll("%length%", "%").replaceAll("%status%", holiday.getStatus())
			, DateTime.parseDate(holiday.getStartStamp())
			, DateTime.parseDate(holiday.getEndStamp())
		);
		for (int i=0; i<entries.size(); i++)
		{
			String new_status = entries.get(i).getTitle().getPlainText().replaceAll(
				holiday.getStatus(),
				status
			); 
			entries.get(i).setTitle(new PlainTextConstruct(new_status));
			updateEntry(entries.get(i));
			cal.update();
		}
		
		ArrayList<Department> deps = emp.getDepartments();
		for (int i=0; i<deps.size(); i++)
		{
			List<CalendarEventEntry> entries2 = getEntries(
				getCalendar(deps.get(i))
				, HOLIDAY_DEPARTMENT_TITLE_FORMAT
					.replaceAll("%length%", "%") // joker for length
					.replaceAll("%status%", holiday.getStatus())
					.replaceAll("%given name%", emp.getField("givenName").toString())
					.replaceAll("%last name%", emp.getField("lastName").toString())
				, DateTime.parseDate(holiday.getStartStamp())
				, DateTime.parseDate(holiday.getEndStamp())
			);
			for (int ii=0; ii<entries2.size(); ii++)
			{
				String new_status = entries2.get(ii).getTitle().getPlainText().replaceAll(
					holiday.getStatus(),
					status
				); 
				entries2.get(ii).setTitle(new PlainTextConstruct(new_status));
				updateEntry(entries2.get(ii));
			}
		}
	}
	
	/**
	 * returns the remaining leaves of the given employee. 
	 * 
	 * @param emp Employee
	 * @param allHolidays [optional] ArrayList with all holidays to avoid google queries
	 * @return float remaining leaves
	 * @throws Exception
	 */
	public float getRemainingLeaves(Employee emp, ArrayList<EgoHoliday> allHolidays) throws Exception
	{
		if (allHolidays == null)
		{
			allHolidays = getHolidays(emp, null);
		}
		float remaining;
		remaining = Float.parseFloat(emp.getField("entitlementLeave").toString());
		remaining += Float.parseFloat(emp.getField("remainingLeavePrevYear").toString());
		for (int i=0; i<allHolidays.size(); i++)
		{
			if (!allHolidays.get(i).getStatus().equals( EgoHoliday.STATUS_DENIED ))
			{
				remaining -= allHolidays.get(i).getLength();
			}
		}
		return remaining;
	}
}
